﻿using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Messaging;
using MSMQMessage = System.Messaging.Message;

namespace Klinkby.MicroBus
{
    [Serializable]
    internal sealed class MuxerQueue : MarshalByRefObject
    {
        readonly static ILog _log = LogManager.GetLogger(typeof(MuxerQueue));
        readonly static MessageQueue _msmq = CreateMessageQueue(); // shared instance
        readonly string _label;
        readonly Queue<MSMQMessage> _lane;
        readonly Action _messageAvailable;
        const string _queuePath = @".\private$\Klinkby.MicroBus";
        
        static MessageQueue CreateMessageQueue()
        {
            bool queueExists = MessageQueue.Exists(_queuePath);
            _log.Info(queueExists ? "Getting queue" : "Creating queue");
            var msmq = queueExists
                ? new MessageQueue(_queuePath)
                : MessageQueue.Create(_queuePath, false);
            msmq.DefaultPropertiesToSend.AttachSenderId = false;
            msmq.DefaultPropertiesToSend.Recoverable = true; // don't store messages in memory
            msmq.Formatter = new BinaryMessageFormatter();

            AppDomain.CurrentDomain.DomainUnload += (sender, e) =>
            {
                _log.Info("Disposing shared message queue reference");
                msmq.Dispose();
            };
            return msmq;
        }

        Queue<MSMQMessage> CreateInbox()
        {
            _log.Debug("Initializing local queue: " + _label);
            var inbox = new Queue<System.Messaging.Message>();
            var msgEnum = _msmq.GetMessageEnumerator3();
            int i = 0;
            foreach (MSMQMessage msg in msgEnum)
            {
                if (msg.Label == _label)
                {
                    inbox.Enqueue(msg);
                     i++;
                }
            }
            _log.Info(_label + " lane has " + i.ToString() + " items");
            return inbox;
        }

        public MuxerQueue(string label, Action messageAvailable)
        {
            _label = label;
            _messageAvailable = messageAvailable;
            _lane = CreateInbox();
        }

        public void Enqueue(IMessage item)
        {
            _log.Debug("Enqueueing " + item.Topic);
            var msg = new System.Messaging.Message(item, _msmq.Formatter)
            {
                Label = _label,
            };
            lock (_msmq)
                _msmq.Send(msg);
            lock (_lane)
                _lane.Enqueue(msg);
            if (_messageAvailable != null)
                _messageAvailable();
        }

        public bool Dequeue(Action<IMessage> command)
        {
            if (command == null)
                throw new ArgumentNullException("command");
            _log.Debug("Dequeueing from local lane");
            MSMQMessage msg;
            bool isEmpty;
            lock (_lane)
            {
                if (isEmpty = IsEmpty)
                    return false;
                msg = _lane.Dequeue();
            }
            ThreadPool.QueueUserWorkItem((o) =>
            {
                try
                {
                    IMessage msgBody = (IMessage)msg.Body;
                    _log.Debug("Processing dequeued " + msgBody.Topic);
                    command(msgBody);
                    _log.Debug("Processing completed, dequeueing from msmq");
                    _msmq.ReceiveById(msg.Id).Dispose();
                    msg.Dispose();
                }
                catch(Exception e)
                {
                    _log.Warn("Reenqueuing. Dequeued processing failed: " + e.ToString());
                    lock (_lane)
                        _lane.Enqueue(msg);
                }
            });
            return !isEmpty;
        }

        public bool IsEmpty
        {
            get
            {
                return _lane.GetEnumerator().MoveNext();
            }
        }
    }
}
